/*
 * Copyright (c) 2009 Project E7
 *
 * This file is part of ZebBowling
 *
 * ZebBowling is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ZebBowling is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ZebBowling. If not, see <http://www.gnu.org/licenses/>.
 **/

package org.zebbowling.control;

import com.sun.spot.io.j2me.radiogram.Radiogram;
import com.sun.spot.io.j2me.radiogram.RadiogramConnection;
import com.sun.spot.util.Utils;
import java.io.IOException;
import javax.microedition.io.Connector;

/**
 * This class is used to connect a SPOT as a controller to a basestation
 *
 * @author Joel Jauregui
 * @version 1.0
 */
public class ControllerConnection extends Thread {
    
    private int port;
    private int conPort;
    private boolean alreadyConnected;
    private ControlHandler ch;
    private RadiogramConnection rcvConn;
    private RadiogramConnection brConn;
    private Radiogram rg;    

    /**
     * Creates a basestation with one port associated with it.
     * @param port Port to open a controller connection on
     */
    public ControllerConnection(int port) {
        super();        
        this.port = port;
        conPort = port % 10;
        alreadyConnected = false;        
        try {            
            brConn = (RadiogramConnection) Connector.open("radiogram://broadcast:" + port);
            rcvConn = (RadiogramConnection) Connector.open("radiogram://:" + port);
            }
        catch(IOException e) {/*ignore*/}       
        }    

    public ControllerConnection(int port, ControlHandler ch) {
        this(port);
        setControlHandler(ch);
        }

    /**
     * Opens a connection and waits till it creates one then begins recieving
     * packets
     */
    public void run() {
        while(!ch.threadDead()) {
            openConnection();
            mainLoop();
            }
        }

     /**
     * Opens a connection on the specified port to connect a controller
     *
     * @param port The port to open a conncetion on
     */
    private void openConnection() {
        try {
            //Create datagrams
            Radiogram dgOut = (Radiogram) brConn.newDatagram(brConn.getMaximumLength());
            rg = (Radiogram) rcvConn.newDatagram(rcvConn.getMaximumLength());
            boolean pinged = false;

            //Send open connection packet till we recieve a request
            while(!pinged) {
                dgOut.reset();
                dgOut.writeByte(PacketTypes.SERVER_AVALIABLE);
                brConn.send(dgOut);
                pinged = rcvConn.packetsAvailable();
                if(ch.threadDead()) {
                    close();
                    return;
                    }
                Utils.sleep(20);
                }

            //Close broadcast connection and recieve request
            rcvConn.receive(rg);
            rg.readByte();
            if(rg.readByte() == PacketTypes.ARE_YOU_THERE) {
                rg.reset();
                brConn.close();
                }
            else
                return;

            //Write connection made packet
            byte c = (byte) conPort;
            rg.write(new byte[]{-1,1,c});
            rcvConn.send(rg);
            alreadyConnected = true;
            }
        catch(IOException ioe) {/*ignore*/}
        }

    /**
     * Main loop once we have established a connection to remote SPOT
     */
    private void mainLoop() {
        while(alreadyConnected)
            try {
                //rcvConn.setTimeout(400);
                while(alreadyConnected) {
                    if(ch.threadDead()) {
                        try {
                            close();
                            }
                        catch(IOException e) {}
                        return;
                        }
                    rg.reset();
                    rcvConn.receive(rg);
                    handlePacket(rg.readByte(), rg);
                    }
                }
            catch (IOException ex) {
                reset();
                ex.printStackTrace();
                }
        }

    /**
     * Returns the controller port number of this connection
     *
     * @return The controller port number of this connection
     */
    public int controllerPort() {
        return conPort;
        }
    
    /**
     * Checks whether this connection is active or not
     * 
     * @return <tt>true</tt> if the connection is still active else false
     */
    public boolean connected() {
        return alreadyConnected;
        }

    /**
     * Returns the <tt>ControlHandler</tt> associated with this connection
     *
     * @return The <tt>ControlHandler</tt> associated with this connection.
     * @see ControlHandler
     */
    public ControlHandler getController() {
        return ch;
        }

    /**
     * Disconnects the currently connected controller and resets this connection.
     */
    synchronized public void disconnect() throws IOException {
        Radiogram dgOut = (Radiogram) rcvConn.newDatagram(rcvConn.getNominalLength());
        dgOut.writeByte(PacketTypes.CLOSE_CONNECTION);
        rcvConn.send(dgOut);
        reset();
        }

    private void close() throws IOException {
        brConn.close();
        rcvConn.close();
        }

    /**
     * Resets this connection so another controller can use it.
     */
    public void reset() {
        try {
            alreadyConnected = false;
            brConn = (RadiogramConnection) Connector.open("radiogram://broadcast:" + port);
            rcvConn = (RadiogramConnection) Connector.open("radiogram://:" + port);
            }
        catch(Exception e) {}
        }

    /**
     * Sets this ControllerConnection's ControlHandler
     *
     * @param ch The ControlHandler to set
     */
    public void setControlHandler(ControlHandler ch) {
        this.ch = ch;
        }

    protected void handlePacket(byte type, Radiogram pkt) throws IOException {
        switch(type) {
            case PacketTypes.DATA_PACK_SEND:
                ch.handleInput(pkt);
                break;
            case PacketTypes.CLOSE_CONNECTION:
				reset();
                break;
            case PacketTypes.MESSAGE:
                rg.reset();
                rg.write(new byte[]{-1,0});
                rcvConn.send(rg);
                break;
            }
        }   

    }
